/**
 * Details panel by Jared Brayshaw
 * 
 * Created for Task 149 on FogBugz:
 * https://msoe.fogbugz.com/default.asp?149
 */
package hydrap2p.gui;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Vector;

import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;

/**
 * A panel that displays details about a selected download.
 * 
 * @author Jared
 */
public class DetailsPanel extends JSplitPane
{	
	private JTable details;		//holds information about the download itself
	private JTable contents;	//contains a list of the files included in the download
	
	//scrollpanes for the two tables
	private static final JScrollPane detailsHalf = new JScrollPane();
	private static final JScrollPane contentsHalf = new JScrollPane();
	
	
	//singleton code
	private static volatile DetailsPanel instance;
	
	public static synchronized DetailsPanel getInstance(){
		if(instance == null){
			synchronized (DetailsPanel.class){
				if(instance == null){
					instance = new DetailsPanel();
				}
			}
		}
		return instance;
	}
	//End of singleton stuff
	
	/**
	 * Creates the panel by putting the details table on the top half of a splitpane and
	 * the contents table on the bottom half.  After setting up the components, the two tables
	 * are refreshed to make sure they have the most recent data.
	 */
	private DetailsPanel(){
		super(JSplitPane.VERTICAL_SPLIT, detailsHalf, contentsHalf);
		
		setOneTouchExpandable(true);
		setDividerLocation(200);		//there's probably a better way to do this, like getting the dimensions of the parent component
		setDividerSize(3);
		
		refreshDetails(null);
	}
	
	/**
	 * Converts a byte amount into string in the format "###.## nB" for display purposes, where 'nB' is the shorthand
	 * for kilobytes, megabytes, etc.  The value passed in will be converted to the most appropriate unit (so that its
	 * value isn't greater than 1000 or less than 1), and the unit will be appended at the end. 
	 * 
	 * @param value	the value (in bytes) to convert to a formatted string
	 * @return a formatted string representing the byte amount passed in
	 */
	private String getByteString(long value)
	{
		//just a list of the units
		ArrayList<String> byteTerms = new ArrayList<String>();
		byteTerms.add("B");
		byteTerms.add("KB");
		byteTerms.add("MB");
		byteTerms.add("GB");
		byteTerms.add("TB");
		byteTerms.add("PB");
		
		//figure out the best way to represent this
		//note that the speeds will be represented in bits because that's how everything is done, even though the actual speed data is in bytes
		int currentTerm = 0;
		Double count = 1 * new Double((Long)value);
		while (count > 1024)
		{
			count /= 1024;
			currentTerm++;
		}
		
		//get rid of the decimal place if it's just bytes
		String stringVal = String.valueOf((Long)value);
		if (currentTerm != 0)
		{
			DecimalFormat twoDForm = new DecimalFormat("#.##");
			stringVal = String.valueOf(twoDForm.format(count));
		}
		stringVal = stringVal + " " + byteTerms.get(currentTerm);
		return stringVal;
	}
	
	/**
	 * Converts a time amount into string in the format "#m, #s" for display purposes.  The return value will
	 * have two terms referring to the most significant time units (years and months, hours and minutes, minutes
	 * and seconds, etc.)
	 * 
	 * @param value	the value (in seconds) to convert to a formatted string
	 * @return a formatted string representing the time amount passed in
	 */
	private String getTimeString(long value)
	{
		//just a list of the units
		ArrayList<String> dateTerms = new ArrayList<String>();
		dateTerms.add("s");
		dateTerms.add("m");
		dateTerms.add("h");
		dateTerms.add("d");
		dateTerms.add("y");
		
		int index = 0;		//we use the terms at index and index - 1
		long lastVal = 0;
		
		//convert to minutes
		if (value > 60)
		{
			lastVal = value % 60;
			value /= 60;
			index = 1;
		}
		
		//convert to hours
		if (value > 60)
		{
			lastVal = value % 60;
			value /= 60;
			index = 2;
		}
		
		//convert to days
		if (value > 24)
		{
			lastVal = value % 24;
			value /= 24;
			index = 3;
		}
		
		//convert to years
		if (value > 365)
		{
			lastVal = value % 365;
			value /= 365;
			index = 4;
		}
		
		//represent the time as a string; assume it's seconds to initialize the variable
		String stringVal = String.valueOf((int)value) + "s";
		
		//if it's more than seconds, convert it to a better form
		if (index > 0)
			stringVal = String.valueOf((int)value) + dateTerms.get(index) + " " + String.valueOf((int)lastVal) + dateTerms.get(index - 1);
		
		return stringVal;
	}
	
	/**
	 * Estimates the amount of time a download will take based on the bytes remaining to download
	 * and the current download speed.
	 * 
	 * @param bytesDown		how many bytes have already been downloaded
	 * @param bytesTotal	how many bytes are in the download
	 * @param speed			how fast the download is being completed
	 * @return				a formatted representation of the estimated time remaining
	 */
	private String getEstTime(long bytesDown, long bytesTotal, int speed)
	{
		if (speed == 0) return "Unknown";
		long bytesLeft = bytesTotal - bytesDown;
		long secondsLeft = bytesLeft / speed;
		return getTimeString(secondsLeft);
	}
	
	/**
	 * Updates the tables based on the contents of the download listing passed in as an argument.
	 * 
	 * @param gdl	the download listing to pull the details from
	 */
	public void refreshDetails(GUIDownloadListing gdl)
	{	
		String[] dColumnNames = {"Attribute", "Value"};
		Object[][] dData = {
				{"Protocol", ""},
				{"Downloaded", ""},
				{"Uploaded", ""},
				{"Ratio", ""},
				{"Est. Time", ""},
				{"Seeders", ""},
				{"Leechers", ""},
				{"Download Location", ""},
				{"Hash Value", ""}
		};
		
		Vector<String> cColumnNames = new Vector<String>();
			cColumnNames.add("Download Contents");
		Vector<Vector<String>> cData = new Vector<Vector<String>>();
		
		if (gdl != null)
		{	System.out.println("GDL is non-null!");
			dData[0][1] = gdl.getProtocol();
			dData[1][1] = getByteString(gdl.getBytesDownloaded());
			dData[2][1] = getByteString(gdl.getBytesUploaded());
			dData[3][1] = (gdl.getBytesUploaded() + 0.0) / (gdl.getBytesDownloaded() + 0.0);
			//dData[4][1] = getEstTime(gdl.getBytesDownloaded(), gdl.getSize(), 0);
			dData[4][1] = gdl.getETAString();
			dData[5][1] = gdl.getSeedCount();
			dData[6][1] = gdl.getPeerCount();
			dData[7][1] = gdl.getDownloadLocation(); 
			dData[8][1] = gdl.getHash();
			
			ArrayList<String> contents = gdl.getContents();
			
			for (String s : contents)
			{
				Vector<String> v = new Vector<String>();
				v.add(s);
				cData.add(v);
			}
		}
				
		//we'll just make new tables each time; the garbage collector should clean up after us
		//also, modify JTable slightly so that the user can't edit these
		details = new JTable(dData, dColumnNames) { public boolean isCellEditable(int rowIndex, int vColIndex) { return false; }};
		contents = new JTable(cData, cColumnNames) { public boolean isCellEditable(int rowIndex, int vColIndex) { return false; }};
		
		detailsHalf.setViewportView(details);
		contentsHalf.setViewportView(contents);
	}
}
